<?php

/**
 *  Base model class
 */
abstract class fvRoot implements ArrayAccess {
	
	/**
	 * fields array, each field has type `field_name` => array (value, type, changed). Not include id field and auto date fields
	 */
	protected $fields;
	protected $key;
	protected $keyName;
	protected $tableName;
	protected $valid;
	
	const UPDATE = 'Update';
	const INSERT = 'Insert';
	protected static $databaseOperation = array (self::UPDATE => DB_AUTOQUERY_UPDATE, self::INSERT => DB_AUTOQUERY_INSERT );
	protected static $serializeTypes = array ('array', 'object' );
	
	/**
	 *
	 */
	function __construct($fields, $tableName, $keyName = "id") {
		$this->fields = $fields;
		$this->tableName = $tableName;
		$this->keyName = $keyName;
		$this->valid = array ();
	}
	
	protected function _setUnChanged($fieds = null) {
		if (is_null ( $fieds )) {
			foreach ( $this->fields as &$field ) {
				$field ['changed'] = false;
			}
		} else {
			if (! is_array ( $fieds ))
				$fieds = array ($fieds );
			
			foreach ( $fieds as $field ) {
				if (isset ( $this->fields [$field] ))
					$this->fields [$field] ["changed"] = false;
				else
					throw new EFieldError ( "No field'{$field}' is specified" );
			}
		}
	}
	
	public static function getSerializedTypes() {
		return self::$serializeTypes;
	}
	
	function getFieldList() {
		return array_keys ( $this->fields );
	}
	
	function isChanged($fieldName) {
		if (isset ( $this->fields [$fieldName] ))
			return $this->fields [$fieldName] ["changed"];
		else
			throw new EFieldError ( "No field '{$fieldName}' is specified" );
	}
	
	function get($fieldName, $defaultValue = '') {
		if (isset ( $this->fields [$fieldName] )) {
			$return = (! is_null ( $this->fields [$fieldName] ["value"] )) ? $this->fields [$fieldName] ["value"] : $defaultValue;
			if (gettype ( $return ) != $this->fields [$fieldName] ["type"] && $this->fields [$fieldName] ["type"] != 'date') {
				@settype ( $return, $this->fields [$fieldName] ["type"] );
			}
			
			return $return;
		} else
			throw new EFieldError ( "No field'{$fieldName}' is specified" );
	
	}
	
	function set($fieldName, $newValue) {
		if (isset ( $this->fields [$fieldName] )) {
			if (($this->fields [$fieldName] ["value"] != $newValue) || ($this->isNew ())) {
				$valid = true;
				
				if (isset ( $this->fields [$fieldName] ["validate"] ) && $this->fields [$fieldName] ["validate"]) {
					$validateMethod = "validate" . ucfirst ( strtolower ( $fieldName ) );
					
					if (method_exists ( $this, $validateMethod )) {
						$valid = $this->$validateMethod ( $newValue );
					}
				}
				if ($valid) {
					@settype ( $newValue, $this->fields [$fieldName] ['type'] );
					
					if ($this->fields [$fieldName] ['pre_func'] && function_exists ( $this->fields [$fieldName] ['pre_func'] )) {
						$newValue = call_user_func ( $this->fields [$fieldName] ['pre_func'], $newValue );
					}
					
					$this->fields [$fieldName] ["value"] = $newValue;
					$this->fields [$fieldName] ["changed"] = true;
				} else {
					$this->fields [$fieldName] ["value"] = $newValue;
					
					return false;
				}
			} else
				return false;
		
		} else
			throw new EFieldError ( "No field '{$fieldName}' is specified" );
		
		return true;
	}
	
	function addField($fieldName, $type, $value = null) {
		if (empty ( $fieldName ) || empty ( $type ))
			throw new EFieldError ( "field name and type are required" );
		
		$this->fields [$fieldName] = array ("value" => $value, "type" => $type, "changed" => false );
	}
	
	function removeField($fieldName) {
		if (empty ( $fieldName ))
			throw new EFieldError ( "field name is required" );
		if (! isset ( $this->fields [$fieldName] ))
			throw new EFieldError ( "No field'{$fieldName}' is specified" );
		
		unset ( $this->fields [$fieldName] );
	}
	
	function __get($name) {
		return $this->get ( $name, null );
	}
	
	function __set($name, $value) {
		return $this->set ( $name, $value );
	}
	
	function save($logging = true) {
		if ($this->isNew ()) {
			return $this->saveToDatabase ( self::INSERT, $logging );
		} else {
			return $this->saveToDatabase ( self::UPDATE, $logging );
		}
		return false;
	}
	
	protected function saveToDatabase($saveType, $logging) {
		if ($this->isValid ()) {
			$insertList = array ();
			
			foreach ( $this->getFieldList () as $field ) {
				if ($this->isChanged ( $field ) || $this->autoValue ( $field, $saveType )) {
					if (in_array ( $this->getFieldType ( $field ), self::$serializeTypes )) {
						$insertList [$field] = serialize ( $this->get ( $field ) );
					} else {
						$insertList [$field] = $this->get ( $field );
					}
				}
			}
			
			if (count ( $insertList ) > 0) {
				if ($saveType == self::INSERT) {
					
					$dbResult = fvSite::$DB->autoExecute ( $this->getTableName (), $insertList, self::$databaseOperation [$saveType] );
				} else {
					
					if (is_array ( $keyNames = $this->getPkName () )) {
						$where = '';
						foreach ( $keyNames as $key ) {
							$where .= (($where) ? " AND " : '') . $key . " = " . $this->getPk ( $key );
						}
					} else {
						$where = $this->getPkName () . " = " . $this->getPk ();
					}
					
					$dbResult = fvSite::$DB->autoExecute ( $this->getTableName (), $insertList, self::$databaseOperation [$saveType], $where );
				}
				if (DB::isError ( $dbResult )) {
					throw new EDatabaseError ( $dbResult->getMessage () );
				}
			}
			
			if ($saveType == self::INSERT) {
				$this->setPk ( fvSite::$DB->getLastId () );
			}
			$this->_setUnChanged ();
			
			if ($logging && $this instanceof iLogger) {
				$this->putToLog ( ($saveType == self::INSERT) ? Log::OPERATION_INSERT : Log::OPERATION_UPDATE );
			}
			
			return true;
		} else {
			if ($logging && $this instanceof iLogger) {
				$this->putToLog ( Log::OPERATION_ERROR );
			}
			
			return false;
		}
	}
	
	function delete() {
		if ($this->isNew ())
			return false;
		
		$primaryKey = $this->getPkName ();
		$tableName = $this->getTableName ();
		
		$phc = fvSite::$DB->Prepare ( "DELETE FROM $tableName WHERE $primaryKey = ?" );
		
		if (DB::isError ( fvSite::$DB->Execute ( $phc, array ($this->getPk () ) ) ))
			throw new EDatabaseError ( "Can't delete record from database." );
		
		if ($this instanceof iLogger) {
			$this->putToLog ( Log::OPERATION_DELETE );
		}
		
		return true;
	}
	
	/**
	 * Fill Object by array
	 *
	 */
	function hydrate($MAP) {
		if (! is_array ( $MAP ))
			throw new EModelError ( "Can't create object from non array" );
		
		foreach ( $MAP as $field => $value ) {
			if (isset ( $this->fields [$field] )) {
				
				if (in_array ( $this->fields [$field] ['type'], self::$serializeTypes ) && (gettype ( $value ) != $this->fields [$field] ['type'])) {
					$value = unserialize ( $value );
				}
				
				@settype ( $value, $this->fields [$field] ['type'] );
				
				$this->fields [$field] ["value"] = $value;
				$this->fields [$field] ["changed"] = false;
			}
		}
		
		if (is_array ( $this->keyName )) {
			foreach ( $this->keyName as $keys ) {
				if (isset ( $MAP [$keys] ))
					$this->key [$keys] = $MAP [$keys];
			}
		} else {
			if (isset ( $MAP [$this->keyName] ))
				$this->key = $MAP [$this->keyName];
		}
		
		$this->valid = array ();
	}
	
	function toHash() {
		$result = array ();
		foreach ( $this->fields as $name => $values ) {
			$result [$name] = $values ['value'];
		}
		
		if (is_array ( $this->keyName )) {
			foreach ( $this->keyName as $keys ) {
				$result [$keys] = $this->key [$keys];
			}
		} else {
			$result [$this->keyName] = $this->key;
		}
		
		return $result;
	}
	
	public function updateFromRequest($REQUEST, $NO_CHANGE = array()) {
		
		foreach ( $this->fields as $fieldName => $field ) {
			switch ($field ['type']) {
				case 'array' :
					if (! in_array ( $fieldName, $NO_CHANGE )) {
						$this->fields [$fieldName] ['value'] = array ();
						$this->fields [$fieldName] ['changed'] = true;
					}
					break;
				case 'bool' :
					if (! in_array ( $fieldName, $NO_CHANGE )) {
						$this->fields [$fieldName] ['value'] = false;
						$this->fields [$fieldName] ['changed'] = true;
					}
					break;
			}
		}
		
		foreach ( $REQUEST as $field => $value ) {
			if (isset ( $this->fields [$field] )) {
				$this->set ( $field, $value );
			}
		}
		return $this->isValid ();
	}
	
	function getPk($keyName = null) {
		if (is_null ( $keyName ))
			return $this->key;
		else
			return $this->key [$keyName];
	}
	
	function setPk($key, $keyName = null) {
		if (is_null ( $keyName ) && ! is_array ( $this->key ))
			$this->key = $key;
		else {
			$this->key [$keyName] = $key;
		}
	}
	
	function getPkName() {
		return $this->keyName;
	}
	
	protected function setPkName($keyName) {
		$this->keyName = $keyName;
	}
	
	function getTableName() {
		return $this->tableName;
	}
	
	protected function setTableName($tableName) {
		$this->tableName = $tableName;
	}
	
	function isValid() {
		return (count ( $this->valid ) == 0);
	}
	
	function getValidationResult() {
		return $this->valid;
	}
	
	function setValidationResult($field, $valid, $message = "") {
		if (! $valid) {
			$this->valid [$field] = fvSite::$fvConfig->get ( "entities.{$this->currentEntity}.fields.{$field}.invalid_string", $message );
		} else if (isset ( $this->valid [$field] )) {
			unset ( $this->valid [$field] );
		}
	}
	
	function autoValue($filedName, $action) {
		if (isset ( $this->fields [$filedName] ['auto_set'] ) && (strtolower ( $this->fields [$filedName] ['auto_set'] ) == strtolower ( $action ) || strtolower ( $this->fields [$filedName] ['auto_set'] ) == "any")) {
			
			switch ($this->fields [$filedName] ['type']) {
				case "integer" :
					if ($this->fields [$filedName] ['auto_value'] == "inc()")
						$this->fields [$filedName] ["value"] ++;
					else if ($this->fields [$filedName] ['auto_value'] == "dec()")
						$this->fields [$filedName] ["value"] --;
					else if ($this->fields [$filedName] ['auto_value'] == "now()")
						$this->fields [$filedName] ["value"] = time ();
					else
						$this->fields [$filedName] ["value"] = intval ( $this->fields [$filedName] ['auto_value'] );
					break;
				case "date" :
					if ($this->fields [$filedName] ['auto_value'] == "now()")
						$this->fields [$filedName] ["value"] = date ( "Y-m-d H:i:s" );
					else
						$this->fields [$filedName] ["value"] = $this->fields [$filedName] ['auto_value'];
					break;
				case "bool" :
					$this->fields [$filedName] ["value"] = ( bool ) $this->fields [$filedName] ['auto_value'];
					break;
				case "string" :
					$this->fields [$filedName] ["value"] = ( string ) $this->fields [$filedName] ['auto_value'];
					break;
			}
			
			return true;
		}
		
		return false;
	}
	
	function isNew() {
		return empty ( $this->key );
	}
	
	function hasField($fieldName) {
		return isset ( $this->fields [$fieldName] );
	}
	
	function getFieldType($fieldName) {
		return $this->fields [$fieldName] ['type'];
	}
	
	function offsetExists($fieldName) {
		return $this->hasField ( $fieldName );
	}
	
	function offsetGet($fieldName) {
		return $this->get ( $fieldName, null );
	}
	
	function offsetUnset($fieldName) {
		return $this->removeField ( $fieldName );
	}
	
	function offsetSet($fieldName, $newValue) {
		if ($this->hasField ( $fieldName ))
			return $this->set ( $fieldName, $newValue );
		else
			$this->addField ( $fieldName, ($newValue) ? gettype ( $newValue ) : 'string', $newValue );
	}
	function getDictionary() {
		$args = func_get_args ();
		$where = $order = "";
		$func_option = '*';
		$field = 'id';
		$field_search = 'id';
		$table = $this->tableName;
		$params = array ();
		
		if (! empty ( $args [0] ))
			$func_option = $args [0];
		if (! empty ( $args [1] ))
			$table = $args [1];
		if (! empty ( $args [2] ))
			$field = $args [2];
		if (! empty ( $args [3] ))
			$field_search = $args [3];
		
		$query = "SELECT $func_option FROM " . $table . " WHERE $field_search=" . $this->$field;
		$res = fvSite::$DB->query ( $query, $params );
		$result = array ();
		if (! DB::isError ( $res )) {
			while ( $row = $res->fetchRow () ) {
				$result [] = $row;
			}
		}
		//        var_dump($result);
		if ($result)
			return $result [0] [$func_option];
		else
			return 'Информация не указана';
	}

}

?>
